home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Source / cache.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-02-21  |  22.7 KB  |  822 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     cache.c
  4.  
  5.     This module manages the article information cache.
  6.     
  7.     Copyright © 1994-1995, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14.  
  15. #include "glob.h"
  16. #include "cache.h"
  17. #include "dialog.h"
  18. #include "memutil.h"
  19. #include "text.h"
  20. #include "fileutil.h"
  21. #include "windutil.h"
  22. #include "resutil.h"
  23. #include "ic.h"
  24.  
  25.  
  26.  
  27. #define kCacheResourceType        'ACCH'
  28. #define kCacheGroupArrayID        128
  29. #define kCacheArticleArrayID    129
  30. #define kCacheStringsBlockID    130
  31.  
  32.  
  33.  
  34. typedef struct TGroupInfo {
  35.     long offset;                /* offset in strings block of group name */
  36.     long numCached;                /* number of cached articles in this group */
  37. } TGroupInfo;
  38.  
  39. typedef struct TArticleInfo {
  40.     long groupIndex;            /* index in group info array, or -1 if entry not used */
  41.     long number;                /* article number */
  42.     long subjectOffset;            /* offset in strings block of subject string */
  43.     long authorOffset;            /* offset in strings block of author string */
  44.     unsigned long creationDateTime;    /* date/time cache entry was created */
  45. } TArticleInfo;
  46.  
  47.  
  48. static Boolean gCacheDirty = false;            /* true if cache changed since read from prefs file */
  49. static TGroupInfo **gGroupInfo = nil;        /* handle to array of group info */
  50. static long gNumGroupInfo = 0;                /* number of elements in group info array */
  51. static TArticleInfo **gArticleInfo = nil;    /* handle to cached article info */
  52. static long gNumArticleInfo = 0;            /* number of elements in article info array */
  53. static Handle gStrings = nil;                /* handle to strings block */
  54. static long gStringsAllocated = 0;            /* number of bytes allocated in strings block */
  55. static long gStringsUsed = 0;                /* number of bytes used in strings block */
  56.     
  57.  
  58.  
  59. /*----------------------------------------------------------------------------
  60.     ValidCache 
  61.     
  62.     Validate the cache.
  63.             
  64.     Exit:    function result = true if no error.
  65. ----------------------------------------------------------------------------*/
  66.  
  67. static Boolean ValidCache (void)
  68. {
  69.     long i, j, numCached;
  70.     TArticleInfo *p;
  71.     TGroupInfo *q;
  72.     unsigned long nowDateTimePlus24Hours;
  73.  
  74.     GetDateTime(&nowDateTimePlus24Hours);
  75.     nowDateTimePlus24Hours += 24L*60L*60L;
  76.  
  77.     if (MyGetHandleSize(gGroupInfo) != gNumGroupInfo*sizeof(TGroupInfo)) goto exit;
  78.     for (i = 0, q = *gGroupInfo; i < gNumGroupInfo; i++, q++) {
  79.         if (q->offset < 0) goto exit;
  80.         if (q->offset >= gStringsUsed) goto exit;
  81.         if (strlen(*gStrings + q->offset) > 255) goto exit;
  82.         numCached = 0;
  83.         for (j = 0, p = *gArticleInfo; j < gNumArticleInfo; j++, p++)
  84.             if (p->groupIndex == i) numCached++;
  85.         if (numCached != q->numCached) goto exit;
  86.     }
  87.     
  88.     if (MyGetHandleSize(gArticleInfo) != gNumArticleInfo*sizeof(TArticleInfo)) goto exit;
  89.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  90.         if (p->groupIndex < 0) goto exit;
  91.         if (p->groupIndex >= gNumGroupInfo) goto exit;
  92.         if (p->number <= 0) goto exit;
  93.         if (p->subjectOffset < 0) goto exit;
  94.         if (p->subjectOffset >= gStringsUsed) goto exit;
  95.         if (strlen(*gStrings + p->subjectOffset) > 255) goto exit;
  96.         if (p->authorOffset < 0) goto exit;
  97.         if (p->authorOffset >= gStringsUsed) goto exit;
  98.         if (strlen(*gStrings + p->authorOffset) > 255) goto exit;
  99.         if (p->creationDateTime > nowDateTimePlus24Hours) goto exit;
  100.     }
  101.     
  102.     return true;
  103.     
  104. exit:
  105.  
  106.     ErrorMessageNumber(kStrDamagedCache);
  107.     return false;
  108. }
  109.     
  110.  
  111.  
  112. /*----------------------------------------------------------------------------
  113.     ReadArticleCache 
  114.     
  115.     Read the article cache from the prefs file.
  116.             
  117.     Exit:    function result = error code.
  118.     
  119.     This function must be called during initialization, when the prefs
  120.     file is read.
  121. ----------------------------------------------------------------------------*/
  122.  
  123. OSErr ReadArticleCache (void)
  124. {
  125.     OSErr err = noErr;
  126.  
  127.     err = MyGet1Resource(kCacheResourceType, kCacheGroupArrayID, &gGroupInfo);
  128.     if (err == resNotFound) {
  129.         err = MyNewHandle(0, &gStrings);
  130.         if (err != noErr) goto exit;
  131.         err = MyNewHandle(0, &gGroupInfo);
  132.         if (err != noErr) goto exit;
  133.         err = MyNewHandle(0, &gArticleInfo);
  134.         if (err != noErr) goto exit;
  135.     } else if (err == noErr) {
  136.         DetachResource((Handle)gGroupInfo);
  137.         gNumGroupInfo = MyGetHandleSize(gGroupInfo) / sizeof(TGroupInfo);
  138.         err = MyGet1Resource(kCacheResourceType, kCacheArticleArrayID, &gArticleInfo);
  139.         if (err != noErr) goto exit;
  140.         DetachResource((Handle)gArticleInfo);
  141.         gNumArticleInfo = MyGetHandleSize(gArticleInfo) / sizeof(TArticleInfo);
  142.         err = MyGet1Resource(kCacheResourceType, kCacheStringsBlockID, &gStrings);
  143.         if (err != noErr) goto exit;
  144.         DetachResource(gStrings);
  145.         gStringsAllocated = gStringsUsed = MyGetHandleSize(gStrings);
  146.         if (!ValidCache()) {
  147.             FlushArticleCache();
  148.             return userCanceledErr;
  149.         }
  150.     } else {
  151.         goto exit;
  152.     }
  153.     return noErr;
  154.     
  155. exit:
  156.  
  157.     MyDisposeHandle(gGroupInfo);
  158.     MyDisposeHandle(gArticleInfo);
  159.     MyDisposeHandle(gStrings);
  160.     gGroupInfo = nil;
  161.     gArticleInfo = nil;
  162.     gStrings = nil;
  163.     gNumGroupInfo = gNumArticleInfo = gStringsAllocated = gStringsUsed = 0;
  164.     gCacheDirty = true;
  165.     return err;
  166. }
  167.  
  168.  
  169.  
  170. /*----------------------------------------------------------------------------
  171.     CompactArticleCache 
  172.     
  173.     Compact the cache.
  174. ----------------------------------------------------------------------------*/
  175.  
  176. void CompactArticleCache (void)
  177. {
  178.     TGroupInfo **groupInfo = nil, *q1, *q2;
  179.     TArticleInfo **articleInfo = nil, *p1, *p2;
  180.     Handle strings = nil;
  181.     long numGroupInfo, numArticleInfo, stringsUsed, size, i, j, k;
  182.     OSErr err = noErr;
  183.     short sLen, aLen;
  184.     unsigned long nowDateTimeMinus60Days;
  185.  
  186.     if (gGroupInfo == nil) return;
  187.     GetDateTime(&nowDateTimeMinus60Days);
  188.     nowDateTimeMinus60Days -= 60L*24L*60L*60L;
  189.  
  190.     MySetHandleSize(gStrings, gStringsUsed);
  191.     
  192.     size = MyGetHandleSize(gGroupInfo);
  193.     err = MyNewHandle(size, &groupInfo);
  194.     if (err != noErr) goto exit;
  195.     numGroupInfo = 0;
  196.     
  197.     size = MyGetHandleSize(gArticleInfo);
  198.     err = MyNewHandle(size, &articleInfo);
  199.     if (err != noErr) goto exit;
  200.     BlockMoveData(*gArticleInfo, *articleInfo, size);
  201.     numArticleInfo = 0;
  202.     
  203.     size = MyGetHandleSize(gStrings);
  204.     err = MyNewHandle(size, &strings);
  205.     if (err != noErr) goto exit;
  206.     stringsUsed = 0;
  207.     
  208.     for (i = 0, j = 0, q1 = *groupInfo, q2 = *gGroupInfo; j < gNumGroupInfo; j++, q2++) {
  209.         if (q2->numCached > 0) {
  210.             q1->offset = stringsUsed;
  211.             q1->numCached = 0;
  212.             strcpy(*strings + stringsUsed, *gStrings + q2->offset);
  213.             stringsUsed += strlen(*strings + stringsUsed) + 1;
  214.             numGroupInfo++;
  215.             q1++;
  216.             if (i != j) {
  217.                 for (k = 0, p1 = *articleInfo; k < gNumArticleInfo; k++, p1++)
  218.                     if (p1->groupIndex == j) p1->groupIndex = i;
  219.             }
  220.             i++;
  221.         }
  222.     }
  223.     MySetHandleSize(groupInfo, numGroupInfo * sizeof(TGroupInfo));
  224.     
  225.     for (j = 0, p1 = *articleInfo, p2 = *articleInfo; j < gNumArticleInfo; j++, p2++) {
  226.         if (p2->groupIndex >= 0 && p2->groupIndex < numGroupInfo &&
  227.             p2->creationDateTime >= nowDateTimeMinus60Days) 
  228.         {
  229.             sLen = strlen(*gStrings + p2->subjectOffset);
  230.             aLen = strlen(*gStrings + p2->authorOffset);
  231.             p1->groupIndex = p2->groupIndex;
  232.             p1->number = p2->number;
  233.             strcpy(*strings + stringsUsed, *gStrings + p2->subjectOffset);
  234.             p1->subjectOffset = stringsUsed;
  235.             stringsUsed += sLen + 1;
  236.             strcpy(*strings + stringsUsed, *gStrings + p2->authorOffset);
  237.             p1->authorOffset = stringsUsed;
  238.             stringsUsed += aLen + 1;
  239.             p1->creationDateTime = p2->creationDateTime;
  240.             (*groupInfo)[p1->groupIndex].numCached++;
  241.             numArticleInfo++;
  242.             p1++;
  243.         }
  244.     }
  245.     MySetHandleSize(articleInfo, numArticleInfo * sizeof(TArticleInfo));
  246.     
  247.     MySetHandleSize(strings, stringsUsed);
  248.     
  249.     MyDisposeHandle(gGroupInfo);
  250.     gGroupInfo = groupInfo;
  251.     gNumGroupInfo = numGroupInfo;
  252.     MyDisposeHandle(gArticleInfo);
  253.     gArticleInfo = articleInfo;
  254.     gNumArticleInfo = numArticleInfo;
  255.     MyDisposeHandle(gStrings);
  256.     gStrings = strings;
  257.     gStringsUsed = gStringsAllocated = stringsUsed;
  258.     
  259.     gCacheDirty = true;
  260.     
  261.     return;
  262.     
  263. exit:
  264.  
  265.     MyDisposeHandle(strings);
  266.     MyDisposeHandle(groupInfo);
  267.     MyDisposeHandle(articleInfo);
  268. }
  269.  
  270.  
  271.  
  272. /*----------------------------------------------------------------------------
  273.     FlushArticleCache 
  274.     
  275.     Flush the cache.
  276. ----------------------------------------------------------------------------*/
  277.  
  278. void FlushArticleCache (void)
  279. {
  280.     if (gGroupInfo == nil) return;
  281.     MySetHandleSize(gGroupInfo, 0);
  282.     gNumGroupInfo = 0;
  283.     MySetHandleSize(gArticleInfo, 0);
  284.     gNumArticleInfo = 0;
  285.     MySetHandleSize(gStrings, 0);
  286.     gStringsUsed = gStringsAllocated = 0;
  287.     gCacheDirty = true;
  288. }
  289.  
  290.  
  291.  
  292. /*----------------------------------------------------------------------------
  293.     WriteArticleCache 
  294.     
  295.     Write the article cache to the prefs file.
  296.     
  297.     Entry:    newsServerAtStartup = news server name at startup.
  298.             
  299.     Exit:    function result = error code.
  300.     
  301.     This function must be called during termination, when the prefs file
  302.     is written.
  303. ----------------------------------------------------------------------------*/
  304.  
  305. OSErr WriteArticleCache (Str255 newsServerAtStartup)
  306. {
  307.     OSErr err = noErr;
  308.     
  309.     MyICReadSharedPrefs(kICNNTPHost);
  310.  
  311.     if (!gCacheDirty) return noErr;
  312.     
  313.     if (gGroupInfo == nil) {
  314.         err = MyNewHandle(0, &gGroupInfo);
  315.         if (err != noErr) return err;
  316.         err = MyNewHandle(0, &gArticleInfo);
  317.         if (err != noErr) return err;
  318.         err = MyNewHandle(0, &gStrings);
  319.         if (err != noErr) return err;
  320.     } else if (!EqualString(gPrefs.newsServerName, newsServerAtStartup, false, true)) {
  321.         FlushArticleCache();
  322.     } else {
  323.         CompactArticleCache();
  324.     }
  325.     
  326.     /* Rewrite the three cache resources on the prefs file. */
  327.     
  328.     err = MyReplaceResource(gGroupInfo, kCacheResourceType, kCacheGroupArrayID, "\p");
  329.     if (err != noErr) return err;
  330.     err = MyReplaceResource(gArticleInfo, kCacheResourceType, kCacheArticleArrayID, "\p");
  331.     if (err != noErr) return err;
  332.     err = MyReplaceResource(gStrings, kCacheResourceType, kCacheStringsBlockID, "\p");
  333.     if (err != noErr) return err;
  334.     return noErr;
  335. }
  336.  
  337.  
  338.  
  339. /*----------------------------------------------------------------------------
  340.     AddCachedArticle 
  341.     
  342.     Add an article to the cache.
  343.     
  344.     Entry:    groupName = group name.
  345.             number = article number.
  346.             subject = subject.
  347.             author = author.
  348.             
  349.     Exit:    function result = error code.
  350. ----------------------------------------------------------------------------*/
  351.  
  352. OSErr AddCachedArticle (char *groupName, long number, char *subject, char *author)
  353. {
  354.     long i;
  355.     TArticleInfo *p;
  356.     TGroupInfo *q;
  357.     long index = -1, groupIndex, subjectOffset, authorOffset, offset;
  358.     OSErr err = noErr;
  359.     short len, sLen, aLen;
  360.     
  361.     if (gGroupInfo == nil) return noErr;
  362.  
  363.     /* Check to see if this article is already in the cache.
  364.        Also get index = index in article info array of a free entry, or
  365.        -1 if none. */
  366.  
  367.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  368.         if (p->groupIndex >= 0) {
  369.             if (number == p->number &&
  370.                 strcmp(groupName, *gStrings + (*gGroupInfo)[p->groupIndex].offset) == 0) 
  371.                     return noErr;
  372.         } else {
  373.             index = i;
  374.         }
  375.     }
  376.     
  377.     /* If necessary, add the group name to the strings block and to the group info array. */
  378.     
  379.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  380.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  381.     }
  382.     if (groupIndex >= gNumGroupInfo) {
  383.         len = strlen(groupName);
  384.         if (gStringsUsed + len + 1 > gStringsAllocated) {
  385.             err = MySetHandleSize(gStrings, gStringsAllocated+1000);
  386.             if (err != noErr) return err;
  387.             gStringsAllocated += 1000;
  388.         }
  389.         strcpy(*gStrings + gStringsUsed, groupName);
  390.         offset = gStringsUsed;
  391.         gStringsUsed += len+1;
  392.         err = MySetHandleSize(gGroupInfo, (gNumGroupInfo+1) * sizeof(TGroupInfo));
  393.         if (err != noErr) return err;
  394.         groupIndex = gNumGroupInfo;
  395.         q = &(*gGroupInfo)[groupIndex];
  396.         q->offset = offset;
  397.         q->numCached = 0;
  398.         gNumGroupInfo++;
  399.     }
  400.     
  401.     /* Add the subject and author strings to the strings block. */
  402.     
  403.     sLen = strlen(subject);
  404.     aLen = strlen(author);
  405.     if (gStringsUsed + sLen + aLen + 2 > gStringsAllocated) {
  406.         err = MySetHandleSize(gStrings, gStringsAllocated+1000);
  407.         if (err != noErr) return err;
  408.         gStringsAllocated += 1000;
  409.     }
  410.     strcpy(*gStrings + gStringsUsed, subject);
  411.     subjectOffset = gStringsUsed;
  412.     gStringsUsed += sLen+1;
  413.     len = strlen(author);
  414.     strcpy(*gStrings + gStringsUsed, author);
  415.     authorOffset = gStringsUsed;
  416.     gStringsUsed += aLen+1;
  417.     
  418.     /* Add the new cache entry to the article info array. */
  419.     
  420.     if (index == -1) {
  421.         err = MySetHandleSize(gArticleInfo, (gNumArticleInfo+1) * sizeof(TArticleInfo));
  422.         if (err != noErr) return err;
  423.         index = gNumArticleInfo;
  424.         gNumArticleInfo++;
  425.     }
  426.     p = &(*gArticleInfo)[index];
  427.     p->groupIndex = groupIndex;
  428.     p->number = number;
  429.     p->subjectOffset = subjectOffset;
  430.     p->authorOffset = authorOffset;
  431.     GetDateTime(&p->creationDateTime);
  432.     
  433.     /* Increment the counter in the group info array. */
  434.     
  435.     (*gGroupInfo)[groupIndex].numCached++;
  436.     
  437.     gCacheDirty = true;
  438.     
  439.     return noErr;
  440. }
  441.  
  442.  
  443.  
  444. /*----------------------------------------------------------------------------
  445.     DeleteCachedArticle 
  446.     
  447.     Remove an article from the cache.
  448.     
  449.     Entry:    groupName = group name.
  450.             number = article number.
  451.             
  452.     Exit:    function result = error code.
  453. ----------------------------------------------------------------------------*/
  454.  
  455. OSErr DeleteCachedArticle (char *groupName, long number)
  456. {
  457.     long groupIndex, i;
  458.     TGroupInfo *q;
  459.     TArticleInfo *p;
  460.  
  461.     if (gGroupInfo == nil) return noErr;
  462.  
  463.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  464.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  465.     }
  466.     if (groupIndex >= gNumGroupInfo) return noErr;
  467.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  468.         if (p->groupIndex == groupIndex && p->number == number) {
  469.             p->groupIndex = -1;
  470.             (*gGroupInfo)[groupIndex].numCached--;
  471.             gCacheDirty = true;
  472.             return noErr;
  473.         }
  474.     }
  475.     return noErr;
  476. }
  477.  
  478.  
  479.  
  480. /*----------------------------------------------------------------------------
  481.     CompareArticleNumbers 
  482.     
  483.     Compare an article number to the article number in a TSubject record.
  484.     
  485.     Entry:    *number = article number.
  486.             x = pointer to TSubject record
  487.             
  488.     Exit:    function result = 
  489.                 -1 if article number < article number in TSubject record.
  490.                 0 if article number = article number in TSubject record.
  491.                 +1 if article number > article number in TSubject record.
  492. ----------------------------------------------------------------------------*/
  493.  
  494. static int CompareArticleNumbers (long *number, TSubject *x)
  495. {
  496.     if (*number < x->number) {
  497.         return -1;
  498.     } else if (*number == x->number) {
  499.         return 0;
  500.     } else {
  501.         return +1;
  502.     }
  503. }
  504.  
  505.  
  506.  
  507. /*----------------------------------------------------------------------------
  508.     AppendOneCachedArticle 
  509.     
  510.     Append one cached article for a group to the end of a subject array.
  511.     
  512.     Entry:    subjectArray = handle to subject array.
  513.             oldNumSubjects = number of elements in original subject array.
  514.             *numSubjects = current number of elements in subject array.
  515.             strings = handle to strings block for subject window.
  516.             number = article number.
  517.             subjectOffset = offset of subject string in strings block.
  518.             authorOffset = offset of author string in strings block.
  519.             
  520.     Exit:    function result = error code.
  521. ----------------------------------------------------------------------------*/
  522.  
  523. static OSErr AppendOneCachedArticle (TSubject **subjectArray, short oldNumSubjects,
  524.     short *numSubjects, Handle strings, long number, long subjectOffset, 
  525.     long authorOffset)
  526. {
  527.     TSubject *x;
  528.     OSErr err = noErr;
  529.     long sOffset, aOffset;
  530.     short sLen, aLen;
  531.     char state;
  532.     
  533.     /* Check to see if this article was already read from the net. */
  534.  
  535.     state = MyHGetState(subjectArray);
  536.     MyHLock(subjectArray);
  537.     x = bsearch(&number, *subjectArray, oldNumSubjects, sizeof(TSubject), 
  538.         (int(*)(const void *, const void *))CompareArticleNumbers);
  539.     MyHSetState(subjectArray, state);
  540.     if (x != nil) return noErr;
  541.     
  542.     /* Check for too many subjects. */
  543.     
  544.     if (*numSubjects >= 16000) {
  545.         ErrorMessageNumber(kStrTooManySubjects);
  546.         return userCanceledErr;
  547.     }
  548.     
  549.     /* Add the subject and author strings to the strings block for the 
  550.        subject window. */
  551.        
  552.     sLen = strlen(*gStrings + subjectOffset);
  553.     aLen = strlen(*gStrings + authorOffset);
  554.     sOffset = GetHandleSize(strings);
  555.     aOffset = sOffset + sLen + 1;
  556.     err = MySetHandleSize(strings, aOffset + aLen + 1);
  557.     if (err != noErr) return err;
  558.     strcpy(*strings + sOffset, *gStrings + subjectOffset);
  559.     strcpy(*strings + aOffset, *gStrings + authorOffset);
  560.        
  561.     /* Add a new TSubject element to the end of the subject array. */
  562.     
  563.     err = MySetHandleSize(subjectArray, (*numSubjects+1)*sizeof(TSubject));
  564.     if (err != noErr) return err;
  565.     x = &(*subjectArray)[*numSubjects];
  566.     x->number = number;
  567.     x->subjectOffset = sOffset;
  568.     x->authorOffset = aOffset;
  569.     x->read = true;
  570.     x->collapsed = gPrefs.showThreadsCollapsed;
  571.     x->inList = true;
  572.     x->drawTriangleFilled = false;
  573.     x->onlyRedrawTriangle = false;
  574.     x->onlyRedrawCheck = false;
  575.     (*numSubjects)++;
  576.     return noErr;
  577. }
  578.  
  579.  
  580.  
  581. /*----------------------------------------------------------------------------
  582.     AppendCachedArticles 
  583.     
  584.     Append all the cached articles for a group to the end of the subject
  585.     array for a subject window.
  586.     
  587.     Entry:    wind = pointer to subject window.
  588.             
  589.     Exit:    function result = error code.
  590. ----------------------------------------------------------------------------*/
  591.  
  592. OSErr AppendCachedArticles (WindowPtr wind)
  593. {
  594.     TWindow **info;
  595.     CStr255 groupName;
  596.     TSubject **subjectArray;
  597.     short numSubjects, oldNumSubjects;
  598.     long groupIndex, i;
  599.     TGroupInfo *q;
  600.     TArticleInfo *p;
  601.     Handle strings;
  602.     OSErr err = noErr;
  603.     
  604.     if (gGroupInfo == nil) return noErr;
  605.     
  606.     info = (TWindow**)GetWRefCon(wind);
  607.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  608.     subjectArray = (**info).subjectArray;
  609.     numSubjects = oldNumSubjects = (**info).numSubjects;
  610.     strings = (**info).strings;
  611.     
  612.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  613.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  614.     }
  615.     if (groupIndex >= gNumGroupInfo) return noErr;
  616.     
  617.     for (i = 0; i < gNumArticleInfo; i++) {
  618.         p = &(*gArticleInfo)[i];
  619.         if (p->groupIndex == groupIndex) {
  620.             err = AppendOneCachedArticle(subjectArray,  oldNumSubjects, 
  621.                 &numSubjects, strings, p->number, p->subjectOffset, 
  622.                 p->authorOffset);
  623.             if (err != noErr) return err;
  624.         }
  625.     }
  626.     
  627.     (**info).numSubjects = numSubjects;
  628.     
  629.     return noErr;
  630. }
  631.  
  632.  
  633.  
  634. /*----------------------------------------------------------------------------
  635.     AgeArticleCache 
  636.     
  637.     Age the cached articles for a group.
  638.     
  639.     Entry:    groupName = group name.
  640.             low = low article number for this group on the server.
  641.             
  642.     Exit:    function result = error code.
  643.                 
  644.     All cached articles for the group with article numbers less than
  645.     the low article number are deleted.
  646. ----------------------------------------------------------------------------*/
  647.  
  648. OSErr AgeArticleCache (char *groupName, long low)
  649. {
  650.     long groupIndex, i;
  651.     TGroupInfo *q;
  652.     TArticleInfo *p;
  653.     
  654.     if (gGroupInfo == nil) return noErr;
  655.  
  656.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  657.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  658.     }
  659.     if (groupIndex >= gNumGroupInfo) return noErr;
  660.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  661.         if (p->groupIndex == groupIndex && p->number < low) {
  662.             p->groupIndex = -1;
  663.             (*gGroupInfo)[groupIndex].numCached--;
  664.             gCacheDirty = true;
  665.         }
  666.     }
  667.     return noErr;
  668. }
  669.  
  670.  
  671.  
  672. /*----------------------------------------------------------------------------
  673.     DumpArticleCacheToFile 
  674.     
  675.     Dump the cache to a text file in human readable format (development
  676.     version only).
  677.     
  678.     Entry:    fSpec = pointer to file spec.
  679.             
  680.     Exit:    function result = error code.
  681. ----------------------------------------------------------------------------*/
  682.  
  683. #ifdef kDevelopmentVersion
  684.  
  685. OSErr DumpArticleCacheToFile (FSSpec *fSpec)
  686. {
  687.     OSErr err = noErr;
  688.     short refNum = 0;
  689.     CStr255 msg;
  690.     Str255 dateString, timeString;
  691.     long len, numCache = 0;
  692.     TArticleInfo *p;
  693.     TGroupInfo *q;
  694.     long i;
  695.     char state1, state2, state3;
  696.     
  697.     state1 = MyHGetState(gArticleInfo);
  698.     state2 = MyHGetState(gArticleInfo);
  699.     state3 = MyHGetState(gStrings);
  700.  
  701.     if (gGroupInfo == nil) {
  702.         ErrorMessage("There is no cache.");
  703.         return userCanceledErr;
  704.     }
  705.  
  706.     err = FSpOpenDF(fSpec, fsRdWrPerm, &refNum);
  707.     if (err != noErr) goto exit;
  708.  
  709.     MyHLock(gArticleInfo);
  710.     MyHLock(gGroupInfo);
  711.     MyHLock(gStrings);
  712.     
  713.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  714.         if (p->groupIndex >= 0) numCache++;
  715.     }
  716.     
  717.     for (i = 0, q = *gGroupInfo; i < gNumGroupInfo; i++, q++) {
  718.         sprintf(msg, "%9ld cached articles from %s\r", q->numCached, *gStrings + q->offset);
  719.         len = strlen(msg);
  720.         MyFSWriteNoCache(refNum, &len, msg, nil);
  721.     }
  722.     
  723.     sprintf(msg, "\r%9ld total articles in cache\r\r%9ld total bytes in cache\r\r",
  724.         numCache, MyGetHandleSize(gGroupInfo) + MyGetHandleSize(gArticleInfo) +
  725.         MyGetHandleSize(gStrings));
  726.     len = strlen(msg);
  727.     MyFSWriteNoCache(refNum, &len, msg, nil);
  728.     
  729.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  730.         if (p->groupIndex >= 0) {
  731.             sprintf(msg, "%ld\r   %s:%ld\r", i, 
  732.                 *gStrings + (*gGroupInfo)[p->groupIndex].offset,
  733.                 p->number);
  734.              len = strlen(msg);
  735.             MyFSWriteNoCache(refNum, &len, msg, nil);
  736.             sprintf(msg, "   %s\r", *gStrings + p->subjectOffset);
  737.             len = strlen(msg);
  738.             MyFSWriteNoCache(refNum, &len, msg, nil);
  739.             sprintf(msg, "   %s\r", *gStrings + p->authorOffset);
  740.             len = strlen(msg);
  741.             MyFSWriteNoCache(refNum, &len, msg, nil);
  742.             IUDateString(p->creationDateTime, shortDate, dateString);
  743.             IUTimeString(p->creationDateTime, false, timeString);
  744.             p2cstr(dateString);
  745.             p2cstr(timeString);
  746.             sprintf(msg, "   Created %s %s\r", dateString, timeString);
  747.             len = strlen(msg);
  748.             MyFSWriteNoCache(refNum, &len, msg, nil);
  749.         } else {
  750.             sprintf(msg, "%ld\r   *** unused ***\r", i);
  751.             len = strlen(msg);
  752.             MyFSWriteNoCache(refNum, &len, msg, nil);
  753.         }
  754.     }
  755.     
  756.     MyHSetState(gArticleInfo, state1);
  757.     MyHSetState(gGroupInfo, state2);
  758.     MyHSetState(gStrings, state3);
  759.     
  760.     MyFSClose(refNum, nil);
  761.     return noErr;
  762.     
  763. exit:
  764.     
  765.     if (refNum != 0) MyFSClose(refNum, nil);
  766.     MyHSetState(gArticleInfo, state1);
  767.     MyHSetState(gGroupInfo, state2);
  768.     MyHSetState(gStrings, state3);
  769.     return err;
  770. }
  771.  
  772. #endif 
  773.  
  774.  
  775.  
  776. /*----------------------------------------------------------------------------
  777.     DisplayArticleCache 
  778.     
  779.     Display the cache in a text window in human readable format (development
  780.     version only).
  781.     
  782.     Exit:    function result = error code.
  783. ----------------------------------------------------------------------------*/
  784.  
  785. #ifdef kDevelopmentVersion
  786.  
  787. OSErr DisplayArticleCache (void)
  788. {
  789.     FSSpec fSpec;
  790.     OSErr err = noErr;
  791.     Handle text = nil;
  792.     short refNum = 0;
  793.     long len;
  794.     WindowPtr wind;
  795.  
  796.     err = CreateTemporaryFile(&fSpec, kNewsWatcherSignature, 'ttxt', 'TEXT');
  797.     if (err != noErr) goto exit;
  798.     err = DumpArticleCacheToFile(&fSpec);
  799.     if (err != noErr) goto exit;
  800.     err = FSpOpenDF(&fSpec, fsRdPerm, &refNum);
  801.     if (err != noErr) goto exit;
  802.     err = MyNewHandle(0x7fff, &text);
  803.     if (err != noErr) goto exit;
  804.     len = 0x7fff;
  805.     MyHLock(text);
  806.     err = FSRead(refNum, &len, *text);
  807.     MyHUnlock(text);
  808.     MySetHandleSize(text, len);
  809.     MyFSClose(refNum, nil);
  810.     err = MakeNewTextWindow("\pArticle Cache", 0, nil, text, &wind);
  811.     if (err != noErr) goto exit;
  812.     MyDisposeHandle(text);
  813.     return noErr;
  814.     
  815. exit:
  816.  
  817.     if (refNum != 0) MyFSClose(refNum, nil);
  818.     MyDisposeHandle(text);
  819.     return err;
  820. }
  821.  
  822. #endif